package org.stars.daostars.sqlmapper.loader.xml;

import static org.stars.daostars.core.conditions.ConditionConjunctionType.AND;
import static org.stars.daostars.core.conditions.ConditionConjunctionType.OR;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.stars.daostars.ConstraintOperator;
import org.stars.daostars.conversion.TypeAlias;
import org.stars.daostars.core.DaoMap;
import org.stars.daostars.core.DaoStars;
import org.stars.daostars.core.Profile;
import org.stars.daostars.core.SqlQuery;
import org.stars.daostars.core.conditions.Condition;
import org.stars.daostars.core.conditions.ConditionConstraint;
import org.stars.daostars.core.conditions.ConditionConstraintType;
import org.stars.daostars.operators.Empty;
import org.stars.daostars.pipeline.DaoPipeline;
import org.stars.daostars.pipeline.DaoPipelineElement;
import org.stars.daostars.pipeline.DaoPipeline.TypeConnection;
import org.stars.daostars.pipeline.DaoPipelineElement.TypeQuery;
import org.stars.daostars.sqlmapper.SqlMapper;
import org.stars.util.StringUtility;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class DaoLoaderHandler extends DefaultHandler  {
	
	public DaoLoaderHandler setFileName(String fileName)
	{
		currentFileName=fileName;
		return this;
	}
	
	public DaoLoaderHandler setCurrentProfile(Profile profile)
	{
		currentProfile=profile;
		return this;
	}
	
	protected Profile currentProfile;
	
	protected String currentFileName;
	
	protected StringBuilder	buffer;
	
	/**
	 * per la definizione delle conditional clause. Serve a indicare se è stato
	 * specificato o meno un ruolo
	 */
	private boolean				roleDefined;

	/**
	 * per la definizione delle conditional clause. Serve a indicare se è stato
	 * specificato o meno una funzione
	 */
	private boolean				functionDefined;
	
	private SqlQuery			currentQuery;

	/**
	 * mappa attualmente caricata
	 */
	private String				currentMapName;

	/**
	 * pipeline attualmente analizzata.
	 */
	private String				currentPipelineName;

	/**
	 * condizioni
	 */
	private String				currentCondition;

	private ConditionConstraint	currentConditionConstraint;

	/**
	 * definizione di dao corrente
	 */
	private SqlMapper		currentDaoDefinition;

	public Log getLog() {
		return LogFactory.getLog(getClass());
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.ContentHandler#startDocument()
	 */
	@Override
	public void startDocument() {
		status = Status.STATUS_UNDEFINED;
		
		currentMapName = "";
		currentMapName = "";
		currentCondition = "";

		functionDefined = false;
		roleDefined = false;

		currentDaoDefinition = new SqlMapper();
	}

	/**
	 * tag analizzato
	 */
	private Status	status;
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.ContentHandler#startElement(java.lang.String,
	 * java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	/* (non-Javadoc)
	 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	@Override
	public void startElement(String uri, String name, String qName, Attributes attributes) throws SAXException {
		Log log = getLog();

		// azzeriamo il buffer.
		buffer = new StringBuilder();

		// tag dao
		if (Tag.DAO.equalsIgnoreCase(qName)) {
			status = Status.STATUS_DAO;
			currentDaoDefinition=DaoTagHandler.startElement(currentProfile,currentFileName, attributes);
		} else if (Tag.QUERY.equalsIgnoreCase(qName)) {
			status = Status.STATUS_QUERY;
			currentQuery=QueryTagHandler.startElement(currentDaoDefinition, attributes);
		} else if (Tag.CONDITIONAL_CLAUSE.equalsIgnoreCase(qName)) {
			status = Status.STATUS_CONDITIONAL_CLAUSE;
			currentCondition = StringUtility.nvl(attributes.getValue("name"), "").toUpperCase().trim();
			Condition clause = new Condition();
			clause.setName(currentCondition);
			currentDaoDefinition.setConditionalClause(currentCondition, clause);

			String sType = StringUtility.nvl(attributes.getValue("conjunction"), "and").trim();
			if ("or".equalsIgnoreCase(sType))
				clause.setType(OR);
			if ("and".equalsIgnoreCase(sType))
				clause.setType(AND);

			roleDefined = false;
			// tag conditional-clause.role
		} else if (Tag.ROLE.equalsIgnoreCase(qName)) {
			status = Status.STATUS_ROLE;
			Condition clause = currentDaoDefinition.getCondition(currentCondition);
			String roles = StringUtility.nvl(attributes.getValue("name"), "*").trim();

			// se è il primo ruolo definito, lo impostiamo con il set,
			// altrimenti lo impostiamo con add.
			if (roleDefined) {
				clause.addRoles(roles);
			} else {
				roleDefined = true;
				clause.setRoles(roles);
			}
		} else if (Tag.FUNCTION.equalsIgnoreCase(qName)) {
			status = Status.STATUS_FUNCTION;
			Condition clause = currentDaoDefinition.getCondition(currentCondition);
			String functions = StringUtility.nvl(attributes.getValue("name"), "*").trim();

			// se è il primo ruolo definito, lo impostiamo con il set,
			// altrimenti lo impostiamo con add.
			if (functionDefined) {
				clause.addFunctions(functions);
			} else {
				functionDefined = true;
				clause.setFunctions(functions);
			}
			// tag conditional-clause.parameter
		} else if (Tag.CONDITION.equalsIgnoreCase(qName)) {
			status = Status.STATUS_CONDITION;

			Condition clause = currentDaoDefinition.getCondition(currentCondition);

			currentConditionConstraint = new ConditionConstraint();
			currentConditionConstraint.setType(ConditionConstraintType.OPERATOR);

			String currentConstraintProperty = StringUtility.nvl(attributes.getValue("property")).toLowerCase().trim();
			String currentConstraintOperator = StringUtility.nvl(attributes.getValue("operator"), "isNotEmpty").toLowerCase().trim();
			String currentConstraintValue = StringUtility.nvl(attributes.getValue("value")).toLowerCase().trim();
			String currentConstraintPropertyValue = StringUtility.nvl(attributes.getValue("propertyValue")).toLowerCase().trim();

			currentConditionConstraint.setName(currentConstraintProperty);
			currentConditionConstraint.setPropertyValue(currentConstraintPropertyValue);
			currentConditionConstraint.setValue(currentConstraintValue);

			if (currentConstraintOperator.startsWith("is")) {
				currentConstraintOperator = currentConstraintOperator.substring("is".length());
			}

			if (currentConstraintOperator.startsWith("not")) {
				currentConstraintOperator = currentConstraintOperator.substring("not".length());
				currentConditionConstraint.setNegate(true);
			}

			// se un operatore e' definito dobbiamo controllare che sia valido.
			if (currentConstraintOperator.length() > 0) {
				ConstraintOperator operator = DaoStars.getInstance().getOperator(currentConstraintOperator);

				if (operator != null) {
					currentConditionConstraint.setOperator(operator);
				} else {
					log.error("Operator " + currentConstraintOperator + " used in dao " + currentDaoDefinition.getName() + " does not exist.");
					// inseriamo un operatore di default
					currentConditionConstraint.setOperator(new Empty());
				}
			} 
			clause.addParameter(currentConditionConstraint);

			// tag variable
		} else if (Tag.VARIABILE.equalsIgnoreCase(qName)) {
			status = Status.STATUS_VARIABLE;
			currentDaoDefinition=VariableTagHandler.startElement(currentDaoDefinition, attributes);

			// tag map
		} else if (Tag.MAP.equalsIgnoreCase(qName)) {
			status = Status.STATUS_MAP;

			currentMapName = StringUtility.nvl(attributes.getValue("name"), "default").toUpperCase();

			DaoMap daoMap = new DaoMap(currentMapName);

			// aggiungiamo il nuovo elemento
			currentDaoDefinition.setMap(currentMapName, daoMap);
			// tag map.attribute
		} else if (Tag.PROPERTY.equalsIgnoreCase(qName)) {
			status = Status.STATUS_MAP_ATTRIBUTE;

			String sTo = StringUtility.nvl(attributes.getValue("name")).toLowerCase().trim();
			String sFrom = StringUtility.nvl(attributes.getValue("from"), attributes.getValue("name")).toLowerCase().trim();
			String sTypeHandler = StringUtility.nvl(attributes.getValue("typehandler")).toUpperCase().trim();

			DaoMap map = currentDaoDefinition.getMap(currentMapName);

			if (sTypeHandler.length() > 0 && !"".equalsIgnoreCase(sTo)) {
				TypeAlias alias = currentDaoDefinition.getTypeAlias(sTypeHandler);

				if (alias == null) {
					log.error("In the map " + currentMapName + " of dao " + currentDaoDefinition.getName() + " for the property " + sTo + " cannot use the type alias "
							+ sTypeHandler + ". Perhaps the alias is wrong?");
				} else {
					map.addPropertyMapping(sTo, sTo, alias.getHandler());
				}
			} else {
				if ("".equalsIgnoreCase(sFrom) || "".equalsIgnoreCase(sTo)) {
					log.error("Parametri insufficienti per la mappatura " + currentMapName + " nel dao " + currentDaoDefinition.getName() + " from: " + sFrom + " to:" + sTo+" typehandler:"+sTypeHandler);
				} else {
					if (map != null) {
						// aggiungiamo la definizione
						map.addPropertyMapping(sTo, sFrom);
					} else {
						log.error("La mappatura " + currentMapName + " non esiste nel dao " + currentDaoDefinition.getName());
					}
				}
			}
		} else if (Tag.PIPELINE.equalsIgnoreCase(qName)) {
			status = Status.STATUS_PIPELINE;
			String sName = StringUtility.nvl(attributes.getValue("name")).toLowerCase().trim();
			currentPipelineName = sName;
			DaoPipeline pipeline = new DaoPipeline();
			pipeline.setName(currentPipelineName);

			// impostiamo il tipo di connessione
			String connectionType = StringUtility.nvl(attributes.getValue("transaction")).toLowerCase().trim();
			TypeConnection tc;
			tc = TypeConnection.SHARED_CONNNECTION;
			if ("true".equalsIgnoreCase(connectionType)) {
				tc = TypeConnection.TRANSACTION;
			}
			pipeline.setTypeConnection(tc);

			currentDaoDefinition.setPipeline(sName, pipeline);

		} else if (Tag.READ.equalsIgnoreCase(qName)) {
			status = Status.STATUS_READ;

			String sQuery = StringUtility.nvl(attributes.getValue("query")).toLowerCase().trim();

			DaoPipelineElement dpe = new DaoPipelineElement();
			dpe.setType(TypeQuery.READ);
			dpe.setQueryName(sQuery);

			DaoPipeline pipeline = currentDaoDefinition.getPipeline(currentPipelineName);
			pipeline.add(dpe);

		} else if (Tag.EXECUTE.equalsIgnoreCase(qName)) {
			status = Status.STATUS_EXECUTE;

			String sQuery = StringUtility.nvl(attributes.getValue("query")).toLowerCase().trim();

			DaoPipelineElement dpe = new DaoPipelineElement();
			dpe.setType(TypeQuery.EXECUTE);
			dpe.setQueryName(sQuery);

			DaoPipeline pipeline = currentDaoDefinition.getPipeline(currentPipelineName);
			pipeline.add(dpe);
		} else if (Tag.EXECUTE_BATCH.equalsIgnoreCase(qName)) {
			status = Status.STATUS_BATCH;

			String sQuery = StringUtility.nvl(attributes.getValue("query")).toLowerCase().trim();

			DaoPipelineElement dpe = new DaoPipelineElement();
			dpe.setType(TypeQuery.BATCH_EXECUTE);
			dpe.setQueryName(sQuery);

			DaoPipeline pipeline = currentDaoDefinition.getPipeline(currentPipelineName);
			pipeline.add(dpe);
		} else if (Tag.INCLUDE.equalsIgnoreCase(qName)) {
			status = Status.STATUS_INCLUDE;
			currentDaoDefinition=IncludeTagHandler.startElement(currentDaoDefinition, attributes);
		} else if (Tag.TYPE_HANDLER.equalsIgnoreCase(qName)) {
			status = Status.STATUS_TYPE_HANDLER;
			currentDaoDefinition=TypeHandlerTagHandler.startElement(currentDaoDefinition, currentQuery, attributes);
		} else {
			status = Status.STATUS_UNDEFINED;
		}
	}

	@Override
	public void endElement(String uri, String name, String qName) throws SAXException {
		if (Tag.QUERY.equalsIgnoreCase(qName)) {			
			QueryTagHandler.endElement(currentQuery,buffer);
		} else if (Tag.CONDITION.equalsIgnoreCase(qName)) {

			// se contiene un'espressione e' di tipo expression
			String sb = buffer.toString();
			sb = sb.toString();

			if (sb.length() > 0) {
				currentConditionConstraint.setExpression(sb);
				currentConditionConstraint.setType(ConditionConstraintType.EXPRESSION);
				currentConditionConstraint.setName("");
				currentConditionConstraint.setPropertyValue("");
				currentConditionConstraint.setValue("");
			}
		} else if (Tag.DAO.equalsIgnoreCase(qName)) {
			DaoTagHandler.endElement(currentDaoDefinition);
		} else if (Tag.CONDITIONAL_CLAUSE.equalsIgnoreCase(qName)) {

		} else {
			status = Status.STATUS_UNDEFINED;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.ContentHandler#characters(char[], int, int)
	 */
	@Override
	public void characters(char ch[], int start, int length) {
		// entra una volta per ogni riga
		String sApp = new String(ch, start, length);

		if (sApp.length() > 0) {
			switch (status) {
			case STATUS_QUERY: // ' query			
				buffer.append(QueryTagHandler.characters(sApp));
				break;
			case STATUS_CONDITION:
				// rimuoviamo gli spazi doppi
				sApp = sApp.replace('\t', ' ');
				sApp = sApp.replace('\n', ' ');
				sApp = sApp.trim();
				sApp = StringUtility.replace(sApp, "  ", " ");

				buffer.append(sApp);
				break;
			default:
				break;
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.ContentHandler#endDocument()
	 */
	@Override
	public void endDocument() {
	}
	
	public SqlMapper getDaoDefinition()
	{
		return currentDaoDefinition;
	}


}
